/*
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted.
 *
 * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * This module provides quick & easy access to the common directories
 * for each operating system.  Support mainly exists for
 * POSIX (XDG Base Directory), but it should also work on
 * Windows (Known Folder) and OS X (Standard Directories), although
 * they're no longer tested.
 *
 * The main goal of this module is to provide a minimal and simple API.
 *
 * ## Example
 *
 * ---
 * import std.stdio : writefln;
 * import std.path : buildPath;
 *
 * import mlib.directories : getProjectDirectories;
 *
 * void main(string[] args)
 * {
 *     ProjectDirectories projectDirs = getProjectDirectories("org", "Example ORG", "My Program");
 *     auto config = readConfig(projectDirs.configDir);
 *
 *     // ...rest of program
 * }
 *
 * auto readConfig(string path)
 * {
 *     // ...some implementation
 * }
 *
 * ---
 *
 *
 * This module supports D version greater than or equal to 2.076.0.
 *
 * Authors: Mio
 * Date: October 26, 2024
 * Homepage: https://codeberg.org/supercell/mlib
 * License: $(LINK2 https://codeberg.org/supercell/mlib/src/branch/trunk/LICENSE, 0BSD)
 * Standards:
 *   $(UL
 *     $(LI $(LINK2 https://specifications.freedesktop.org/basedir-spec/0.8/, XDG Base Directory Specification 0.8))
 *     $(LI $(LINK2 https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb776911(v=vs.85), Known Folders))
 *     $(LI $(LINK2 https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6, macOS Standard Directories))
 *    )
 * Version: 0.5.1
 *
 * History:
 *      0.X.X was the initial version (June 12, 2021)
 *
 *      0.1.0 adds support for runtime and state (February 18, 2023)
 *
 *      0.2.0 Re-wrote the API. Now supports Windows. Bump minimum D version to 2.076.0 (March 26, 2023)
 *
 *      0.3.0 Add support for macOS. Remove old API (July 13, 2023).
 *
 *      0.4.0 Mark getBaseDirectories, getUserDirectories, and getProjectDirectories as @safe (April 2, 2024)
 *
 *      0.5.0 Add stateDir to BaseDirectories and ProjectDirectories (currently POSIX only) (September 24, 2024)
 *
 *      0.5.1 Add stateDir to BaseDirectories and ProjectDirectories (macOS support) (October 26, 2024)
 */
module mlib.directories;

///
/// Provides paths of user-invisible standard directories, following the
/// conventions of the operating system the library is running on.
///
/// To compute the location of cache, config or data directories for
/// individual projects or applications, use ProjectDirectories instead.
///
/// ## Examples
///
/// All examples on this page are computed with a user named
/// $(I Elq).
///
/// An example of `BaseDirectories#configDir`:
///
/// $(UL
///   $(LI $(B Posix): `/home/elq/.config`)
///   $(LI $(B macOS): `/Users/Elq/Library/Preferences`)
///   $(LI $(B Windows): `C:\Users\Elq\AppData\Roaming`)
/// )
///
struct BaseDirectories
{
    ///
    /// Returns the path to the user's home directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$HOME`)
    ///     $(TD /home/elq)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME`)
    ///     $(TD /Users/Elq)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_Profile`)
    ///     $(TD C:\Users\Elq)
    ///   )
    /// )
    ///
    immutable string homeDir;

    ///
    /// Returns the path to the user's cache directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_CACHE_HOME` (fallback: `$HOME/.cache`))
    ///     $(TD /home/elq/.cache)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME/Library/Caches`)
    ///     $(TD /Users/Elq/Library/Caches)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_LocalAppData`)
    ///     $(TD C:\Users\Elq\AppData\Local)
    ///   )
    /// )
    ///
    immutable string cacheDir;

    ///
    /// Returns the path to the user's configuration directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_CONFIG_HOME` (fallback: `$HOME/.config`))
    ///     $(TD /home/elq/.config)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME/Library/Application Support`)
    ///     $(TD /Users/Elq/Library/Application Support)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_RoamingAppData`)
    ///     $(TD C:\Users\Elq\AppData\Roaming)
    ///   )
    /// )
    ///
    immutable string configDir;

    ///
    /// Returns the path to the user's data directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_DATA_HOME` (fallback: `$HOME/.local/share`))
    ///     $(TD /home/elq/.local/share)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME/Library/Application Support`)
    ///     $(TD /Users/Elq/Library/Application Support)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_RoamingAppData`)
    ///     $(TD C:\Users\Elq\AppData\Roaming)
    ///   )
    /// )
    ///
    immutable string dataDir;

    ///
    /// Returns the path to the user's local data directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_DATA_HOME` (fallback: `$HOME/.local/share`))
    ///     $(TD /home/elq/.local/share)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME/Library/Application Support`)
    ///     $(TD /Users/Elq/Library/Application Support)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_LocalAppData`)
    ///     $(TD C:\Users\Elq\AppData\Local)
    ///   )
    /// )
    ///
    immutable string dataLocalDir;

    ///
    /// Returns the path to the user's local executable directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$HOME/.local/bin`)
    ///     $(TD /home/elq/.local/bin)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD N/A)
    ///     $(TD `""`)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD N/A)
    ///     $(TD `""`)
    ///   )
    /// )
    ///
    immutable string executableDir;

    ///
    /// Returns the path to the user's preference directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_CONFIG_HOME` (fallback: `$HOME/.config`))
    ///     $(TD /home/elq/.config)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD `$HOME/Library/Preferences`)
    ///     $(TD /Users/Elq/Library/Preferences)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD `FOLDERID_RoamingAppData`)
    ///     $(TD C:\Users\Elq\AppData\Roaming)
    ///   )
    /// )
    ///
    immutable string preferenceDir;

    ///
    /// Returns the path to the user's runtime directory.
    ///
    /// $(TABLE
    ///   $(TR
    ///     $(TH Platform)
    ///     $(TH Value)
    ///     $(TH Example)
    ///   )
    ///   $(TR
    ///     $(TD Posix)
    ///     $(TD `$XDG_RUNTIME_DIR`)
    ///     $(TD /run/user/1000)
    ///   )
    ///   $(TR
    ///     $(TD macOS)
    ///     $(TD N/A)
    ///     $(TD `""`)
    ///   )
    ///   $(TR
    ///     $(TD Windows)
    ///     $(TD N/A)
    ///     $(TD `""`)
    ///   )
    /// )
    ///
    immutable string runtimeDir;

    ///
    /// Returns the path to the user's state directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_STATE_HOME` (fallback: `$HOME/.local/state/`))
    ///   $(TD /home/elq/.local/state/)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Application Support`)
    ///   $(TD /User/Elq/Library/Application Support)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    /// )
    ///
    immutable string stateDir;

    string toString() const @safe pure nothrow
    {
        version (OSX) {
            enum platform = "OSX";
        } else version (Posix) {
            enum platform = "Posix";
        } else version (Windows) {
            enum platform = "Windows";
        }

        return "BaseDirectories(" ~ platform ~ "):\n" ~
            "  homeDir       = '" ~ homeDir ~ "'\n" ~
            "  cacheDir      = '" ~ cacheDir ~ "'\n" ~
            "  configDir     = '" ~ configDir ~ "'\n" ~
            "  dataDir       = '" ~ dataDir ~ "'\n" ~
            "  dataLocalDir  = '" ~ dataLocalDir ~ "'\n" ~
            "  executableDir = '" ~ executableDir ~ "'\n" ~
            "  preferenceDir = '" ~ preferenceDir ~ "'\n" ~
            "  runtimeDir    = '" ~ runtimeDir ~ "'\n" ~
            "  stateDir      = '" ~ stateDir   ~ "'\n";
    }
}

///
/// Returns a new instance of `BaseDirectories`.
///
/// The instance is an immutable snapshop of the state of the system at
/// the time this function is called.  Subsequent changes to the state
/// of the system are not reflected in instances created prior to such
/// a change.
///
nothrow BaseDirectories getBaseDirectories() @safe
{
    // OS X first so it doesn't get mixed with Posix.
    version (OSX) {
        immutable homeDir = posixHome();
        immutable appSupport = buildPath(homeDir, "Library", "Application Support");
        return BaseDirectories(
            homeDir,
            buildPath(homeDir, "Library", "Caches"),
            appSupport,
            appSupport,
            appSupport,
            "",
            buildPath(homeDir, "Library", "Preferences"),
            "",
            appSupport
        );
    } else version (Posix) {
        return BaseDirectories(
            posixHome(),
            xdgCache(),
            xdgConfig(),
            xdgData(),
            xdgData(),
            // from spec:
            //   User-specific executable files may be stored in $HOME/.local/bin.
            buildPath(posixHome(), ".local", "bin"),
            xdgConfig(),
            xdgRuntime(),
            xdgStateDir()
        );
    } else version (Windows) {
        string dataDir = windowsRoamingData();
        string localDataDir = windowsLocalData();

        return BaseDirectories(
            windowsHome(),
            localDataDir,
            dataDir,
            dataDir,
            localDataDir,
            "",
            dataDir,
            "",
            ""
        );
    } else {
        static assert(false, "mlib.directories: Unsupported operating system.");
    }
}

///
unittest
{
    import std.stdio : writeln;

    BaseDirectories baseDirs = getBaseDirectories();
    writeln(baseDirs);
}

///
/// `ProjectDirectories ` computes the location of cache, config, or
/// data directories for a specific application, which are derived from
/// the standard directories and the name of the project/organisation.
///
/// ## Examples
///
/// All examples in this section are computed with a user named *Elq*,
/// and a `ProjectDirectories` instance created with the following
/// information:
///
/// ```d
/// ProjectDirectories projectDirs = getProjectDirectories("com", "Foo Corp", "Bar App");
/// ```
///
/// Example of `ProjectDirectories#configDir` value in different
/// operating systems:
///
/// $(UL
///  $(LI $(B Posix): `/home/elq/.config/barapp`)
///  $(LI $(B macOS): `/Users/Elq/Library/Preferences/com.Foo-Corp.Bar-App`)
///  $(LI $(B Windows): `C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config`)
/// )
///
struct ProjectDirectories
{
    ///
    /// The path to the project's cache directory in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_CACHE_HOME/<project_path>` (fallback: `$HOME/.cache/<project_path>`))
    ///   $(TD /home/elq/.cache/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Caches/<project_path>`)
    ///   $(TD /Users/Elq/Library/Caches/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_LocalAppData\<project_path>\cache`)
    ///   $(TD C:\Users\Elq\AppData\Local\Foo Corp\Bar App\cache )
    ///   )
    /// )
    ///
    immutable string cacheDir;

    ///
    /// The path to the project's configuration directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_CONFIG_HOME/<project_path>` (fallback: `$HOME/.config/<project_path>/`))
    ///   $(TD /home/elq/.config/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Application Support/<project_path>`)
    ///   $(TD /Users/Elq/Library/Application Support/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_RoamingAppData\<project_path>\config`)
    ///   $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config )
    ///   )
    /// )
    ///
    immutable string configDir;

    ///
    /// The path to the project's data directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DATA_HOME/<project_path>` (fallback: `$HOME/.local/share/<project_path>/`))
    ///   $(TD /home/elq/.local/share/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Application Support/<project_path>`)
    ///   $(TD /Users/Elq/Library/Application Support/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_RoamingAppData\<project_path>\data`)
    ///   $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\data )
    ///   )
    /// )
    ///
    immutable string dataDir;

    ///
    /// The path to the project's local data directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DATA_HOME/<project_path>` (fallback: `$HOME/.local/share/<project_path>/`))
    ///   $(TD /home/elq/.local/share/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Application Support/<project_path>`)
    ///   $(TD /Users/Elq/Library/Application Support/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_LocalAppData\<project_path>\data`)
    ///   $(TD C:\Users\Elq\AppData\Local\Foo Corp\Bar App\data )
    ///   )
    /// )
    ///
    immutable string dataLocalDir;

    ///
    /// The path to the project's preference directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_CONFIG_HOME/<project_path>` (fallback: `$HOME/.config/<project_path>/`))
    ///   $(TD /home/elq/.config/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Preferences/<project_path>`)
    ///   $(TD /Users/Elq/Library/Preferences/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_RoamingAppData\<project_path>\config`)
    ///   $(TD C:\Users\Elq\AppData\Roaming\Foo Corp\Bar App\config)
    ///   )
    /// )
    ///
    immutable string preferenceDir;

    ///
    /// The path to the project's data directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_RUNTIME_DIR/<project_path>`)
    ///   $(TD /run/user/1001/bareapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    /// )
    ///
    immutable string runtimeDir;

    ///
    /// The path to the project's state directory, in which
    /// `<project_path>` is the value of `ProjectDirectories#projectPath`.
    ///
    /// The state directory should contain information/data that persists
    /// between application session, but does not need to be transferred
    /// between machines.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_STATE_HOME/<project_path>/` (fallback: `$HOME/.local/state/<project_path>/`))
    ///   $(TD /home/elq/.local/state/barapp)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Application Support/<project_path>`)
    ///   $(TD /Users/Elq/Library/Application Support/com.Foo-Corp.Bar-App)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    /// )
    ///
    immutable string stateDir;

    ///
    /// The project path fragment used to compute the project's
    /// cache/config/data directories.
    ///
    /// The value is derived from the arguments provided to the
    /// `getProjectDirectories()` function and is platform-dependent.
    ///
    immutable string projectPath;

    string toString() const @safe pure nothrow
    {
        version (OSX) {
            enum platform = "OSX";
        } else version (Posix) {
            enum platform = "Posix";
        } else version (Windows) {
            enum platform = "Windows";
        }

        return "ProjectDirectories(" ~ platform ~ ")\n" ~
            "  projectPath   = '" ~ projectPath ~ "'\n" ~
            "  cacheDir      = '" ~ cacheDir ~ "'\n" ~
            "  configDir     = '" ~ configDir ~ "'\n" ~
            "  dataDir       = '" ~ dataDir ~ "'\n" ~
            "  dataLocalDir  = '" ~ dataLocalDir ~ "'\n" ~
            "  preferenceDir = '" ~ preferenceDir ~ "'\n" ~
            "  runtimeDir    = '" ~ runtimeDir ~ "'\n" ~
            "  stateDir      = '" ~ stateDir   ~ "'\n";
    }
}

///
/// Return an instance of `ProjectDirectories` from values describing
/// the project.
///
/// Params:
///   qualifier =    The reverse domain name notation of the application,
///                  excluding the organisation or application name itself. $(BR)
///                  An example string can be passed if no qualifier should
///                  be used (only affects macOS). $(BR)
///                  Example values: `"com.example"`, `"org"`, `"co.uk"`, `""`.
///
///   organisation = The name of the organisation that develops this application,
///                  or for which the application is developed.$(BR)
///                  An empty string can be passed if no organisation should be
///                  used (only affects macOS and Windows).$(BR)
///                  Example values: `"Foo Corp"`, `"Alice and Bob Inc"`, `""`.
///
///   application =  The name of the application itself.$(BR)
///                  Example values: `"Bar App"`, `"ExampleProgram"`, `"Unicorn-Programme"`.
///
/// Returns: An instance of `ProjectDirectories`, whose directory field values are
///          based on the `qualifier`, `organisation`, and `application` arguments.
///
ProjectDirectories getProjectDirectories(string qualifier, string organisation, string application) nothrow @safe
in
{
    assert(!empty(organisation) && !empty(application),
        "The organisation and the application arguments cannot both be empty");
}
do
{
    version (OSX)
    {
        import std.array : appender;

        // Build the path.
        auto app = appender!string;
        bool hasQual = !empty(qualifier);
        bool hasOrg = !empty(organisation);
        bool hasApp = !empty(application);

        if (hasQual) {
            app ~= replaceAndLower(qualifier, ' ', '-');
            if (hasOrg || hasApp) {
                app ~= '.';
            }
        }
        if (hasOrg) {
            app ~= replaceAndLower(organisation, ' ', '-');
            if (hasApp) {
                app ~= '.';
            }
        }
        if (hasApp) {
            app ~= replaceAndLower(application, ' ', '-');
        }

        immutable path = app.data();
        immutable homeDir = posixHome();
        immutable appSupport = buildPath(homeDir, "Library", "Application Support");

        return ProjectDirectories(
            buildPath(homeDir, "Caches", path),
            buildPath(appSupport, path),
            buildPath(appSupport, path),
            buildPath(appSupport, path),
            buildPath(homeDir, "Library", "Preferences", path),
            "",
            buildPath(appSupport, path),
            path
        );
    }
    else version (Posix)
    {
        import std.uni : isWhite, toLower;

        // Yes, we could call toLower(replace(string(application), " ", "-"))
        // but that could throw. This is nothrow.
        string subPath;
        bool reachedNonWhitespace = false;

        foreach(c; application) {
            if (' ' == c) {
                // Check it's not a newline or something silly.
                if (reachedNonWhitespace && false == isWhite(c)) {
                    subPath ~= '-';
                    // We only want one '-', so make sure
                    // to skip any succeeding spaces.
                    reachedNonWhitespace = false;
                }
            } else {
                subPath ~= toLower(c);
                reachedNonWhitespace = true;
            }
        }

        string configDir = buildPath(xdgConfig(), subPath);
        string dataDir = buildPath(xdgData(), subPath);

        return ProjectDirectories(
            buildPath(xdgCache(), subPath),
            configDir,
            dataDir,
            dataDir,
            configDir,
            xdgRuntime(subPath),
            buildPath(xdgStateDir(), subPath),
            subPath
        );
    }
    else version (Windows)
    {
        bool hasOrg = !empty(organisation);
        bool hasApp = !empty(application);
        string subPath;
        if (hasOrg)
        {
            subPath = organisation;
            if (hasApp)
            {
                subPath ~= "\\";
            }
        }
        if (hasApp)
        {
            subPath ~= application;
        }

        string roamingAppData = buildPath(GetKnownFolder(FOLDERID_RoamingAppData), subPath);
        string localAppData = buildPath(GetKnownFolder(FOLDERID_LocalAppData), subPath);
        string configDir = buildPath(roamingAppData, "config");

        return ProjectDirectories(
            buildPath(localAppData, "cache"),
            configDir,
            buildPath(roamingAppData, "data"),
            buildPath(localAppData, "data"),
            configDir,
            "",
            "",
            subPath
        );
    }
    else
    {
        static assert(false, "mlib.directories: Unsupported operating system.");
    }
}

///
unittest
{
    import std.stdio : writeln;

    ProjectDirectories projectDirs = getProjectDirectories("net", "Sporadic Programmers", "Foo bar-baz");
    writeln(projectDirs);
}

///
/// Provides the paths of user-facing standard directories, following
/// the conventions of the operating system the library is running on.
///
/// ## Examples
///
/// All examples in this section are computed with a user named $(I Elq).
///
/// Example of `UserDirectories#audioDir` value in different operating systems:
///
/// $(UL
///  $(LI $(B Posix): `/home/elq/Music`)
///  $(LI $(B macOS): `/Users/Elq/Music`)
///  $(LI $(B Windows): `C:\Users\Elq\Music`)
/// )
///
struct UserDirectories
{
    ///
    /// The path to the user's home directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$HOME`)
    ///   $(TD /home/elq)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME`)
    ///   $(TD /Users/Elq)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Profile`)
    ///   $(TD C:\Users\Elq)
    ///   )
    /// )
    ///
    immutable string homeDir;

    ///
    /// The path to the user's audio directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_MUSIC_DIR`)
    ///   $(TD /home/elq/Music)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Music`)
    ///   $(TD /Users/Elq/Music)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Music`)
    ///   $(TD C:\Users\Elq\Music)
    ///   )
    /// )
    ///
    immutable string audioDir;

    ///
    /// The path to the user's desktop directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DESKTOP_DIR`)
    ///   $(TD /home/elq/Desktop)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Desktop`)
    ///   $(TD /Users/Elq/Desktop)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Desktop`)
    ///   $(TD C:\Users\Elq\Desktop)
    ///   )
    /// )
    ///
    immutable string desktopDir;

    ///
    /// The path to the user's documents directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DOCUMENTS_DIR`)
    ///   $(TD /home/elq/Documents)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Documents`)
    ///   $(TD /Users/Elq/Documents)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Documents`)
    ///   $(TD C:\Users\Elq\Documents)
    ///   )
    /// )
    ///
    immutable string documentDir;

    ///
    /// The path to the user's download directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DOWNLOAD_DIR`)
    ///   $(TD /home/elq/Downloads)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Downloads`)
    ///   $(TD /Users/Elq/Downloads)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Downloads`)
    ///   $(TD C:\Users\Elq\Downloads)
    ///   )
    /// )
    ///
    immutable string downloadDir;

    ///
    /// The path to the user's fonts directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_DATA_HOME/fonts` (fallback: `$HOME/.local/share/fonts`))
    ///   $(TD /home/elq/.local/share/fonts)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Library/Fonts`)
    ///   $(TD /Users/Elq/Library/Fonts)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    /// )
    ///
    immutable string fontDir;

    ///
    /// The path to the user's pictures directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_PICTURES_DIR`)
    ///   $(TD /home/elq/Pictures)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Pictures`)
    ///   $(TD /Users/Elq/Pictures)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Pictures`)
    ///   $(TD C:\Users\Elq\Pictures)
    ///   )
    /// )
    ///
    immutable string pictureDir;

    ///
    /// The path to the user's public directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_PUBLICSHARE_DIR`)
    ///   $(TD /home/elq/Public)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Public`)
    ///   $(TD /Users/Elq/Public)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Public`)
    ///   $(TD C:\Users\Public)
    ///   )
    /// )
    ///
    immutable string publicDir;

    ///
    /// The path to the user's template directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_TEMPLATES_DIR`)
    ///   $(TD /home/elq/Templates)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD N/A)
    ///   $(TD `""`)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Templates`)
    ///   $(TD C:\Users\Elq\AppData\Roaming\Microsoft\Windows\Templates)
    ///   )
    /// )
    ///
    immutable string templateDir;

    ///
    /// The path to the user's video directory.
    ///
    /// $(TABLE
    ///  $(TR
    ///   $(TH Platform)
    ///   $(TH Value)
    ///   $(TH Example)
    ///   )
    ///  $(TR
    ///   $(TD Posix)
    ///   $(TD `$XDG_VIDEOS_DIR`)
    ///   $(TD /home/elq/Videos)
    ///   )
    ///  $(TR
    ///   $(TD macOS)
    ///   $(TD `$HOME/Movies`)
    ///   $(TD /Users/Elq/Movies)
    ///   )
    ///  $(TR
    ///   $(TD Windows)
    ///   $(TD `FOLDERID_Videos`)
    ///   $(TD C:\Users\Elq\Videos)
    ///   )
    /// )
    ///
    immutable string videoDir;

    string toString() const @safe pure nothrow
    {
        version (OSX)
        {
            enum platform = "OSX";
        }
        else version (Posix)
        {
            enum platform = "Posix";
        }
        else version (Windows)
        {
            enum platform = "Windows";
        }
        return "UserDirectories(" ~ platform ~ ")\n" ~
            "  homeDir     = '" ~ homeDir ~ "'\n" ~
            "  audioDir    = '" ~ audioDir ~ "'\n" ~
            "  desktopDir  = '" ~ desktopDir ~ "'\n" ~
            "  documentDir = '" ~ documentDir ~ "'\n" ~
            "  downloadDir = '" ~ downloadDir ~ "'\n" ~
            "  fontDir     = '" ~ fontDir ~ "'\n" ~
            "  pictureDir  = '" ~ pictureDir ~ "'\n" ~
            "  publicDir   = '" ~ publicDir ~ "'\n" ~
            "  templateDir = '" ~ templateDir ~ "'\n" ~
            "  videoDir    = '" ~ videoDir ~ "'\n";
    }
}

///
/// Get a new instance of `UserDirectories`.
///
/// The instance is an immutable snapshop of the current state of the
/// system at the time this function was called.  Subsequent changes
/// to the state of the system are not reflected in instances created
/// prior to such a change.
///
nothrow UserDirectories getUserDirectories() @safe
{
    version (OSX) {
        immutable homeDir = posixHome();
        return UserDirectories(
            homeDir,
            buildPath(homeDir, "Music"),
            buildPath(homeDir, "Desktop"),
            buildPath(homeDir, "Documents"),
            buildPath(homeDir, "Downloads"),
            buildPath(homeDir, "Library", "Fonts"),
            buildPath(homeDir, "Pictures"),
            buildPath(homeDir, "Public"),
            "",
            buildPath(homeDir, "Movies")
        );
    } else version (Posix) {
        // Fallbacks are from on
        // https://cgit.freedesktop.org/xdg/xdg-user-dirs/tree/user-dirs.defaults
        return UserDirectories(
            posixHome(),
            xdgDir("MUSIC", buildPath(posixHome(), "Music")),
            xdgDir("DESKTOP", buildPath(posixHome(), "Desktop")),
            xdgDir("DOCUMENTS", buildPath(posixHome(), "Documents")),
            xdgDir("DOWNLOAD", buildPath(posixHome(), "Downloads")),
            buildPath(xdgData(), "fonts"),
            xdgDir("PICTURES", buildPath(posixHome(), "Pictures")),
            xdgDir("PUBLICSHARE", buildPath(posixHome(), "Public")),
            xdgDir("TEMPLATES", buildPath(posixHome(), "Templates")),
            xdgDir("VIDEOS", buildPath(posixHome(), "Videos"))
        );
    } else version (Windows) {
        return UserDirectories(
            windowsHome(),
            GetKnownFolder(FOLDERID_Music),
            GetKnownFolder(FOLDERID_Desktop),
            GetKnownFolder(FOLDERID_Documents),
            GetKnownFolder(FOLDERID_Downloads),
            "",
            GetKnownFolder(FOLDERID_Pictures),
            GetKnownFolder(FOLDERID_Public),
            GetKnownFolder(FOLDERID_Templates),
            GetKnownFolder(FOLDERID_Videos)
        );
    } else {
        static assert(false, "mlib.directories: Unsupported operating system.");
    }
}

///
unittest
{
    import std.stdio : writeln;
    UserDirectories userDirs = getUserDirectories();
    writeln(userDirs);
}

private string replaceAndLower(string input, char bad, char good) nothrow @safe
{
    import std.array : appender;
    import std.string : toLower;

    if (empty(input)) {
        return input;
    }

    string res;
    bool charsBefore = false;

    foreach(c; input) {
        if (c == bad) {
            if (charsBefore) {
                res ~= good;
                charsBefore = false;
            }
        } else {
            res ~= toLower(c);
            charsBefore = true;
        }
    }

    return res;
}

///
unittest
{
    string str = "this is my    string";
    string rpl = replaceAndLower(str, ' ', '_');
    assert("this_is_my_string" == rpl);
}

private:

version (Posix)
{
   import std.path : buildPath, isAbsolute;
   import std.process : environment;
   import std.string : empty, fromStringz;

   immutable(string) posixHome() nothrow @trusted
   {
      try {
         const homeDir = environment.get("HOME");
         if (homeDir !is null) {
            return homeDir;
         }
      } catch (Exception e) {
         assert(0, "environment.get should not throw on POSIX");
      }

      const(char)* pwdHome = _posix_fallback_home();
      if (null is pwdHome) {
         return "";
      }
      return cast(string)(fromStringz(pwdHome)).dup;
   }

   ///
   /// Retrieve the XDG User Directory for the specified $(I dirName).
   ///
   /// This will attempt to read the XDG_$(I dirName)_DIR environment
   /// variable.  If it is not found, then we attempt to call the
   /// xdg-user-dir program.  Should this not be installed, then
   /// the result of $(I fallback) (if provided) is returned or the
   /// empty string.
   ///
   /// Please note: If xdg-user-dir is installed, then $(I fallback)
   /// will never be called. xdg-user-dir always succeeds and will
   /// return the user's home directory if $(I dirName) isn't a valid
   /// value.
   ///
   /// Params:
   ///   dirName = The XDG Directory name.
   ///   fallback = The fallback to use if the environment variable
   ///              is not set and xdg-user-dir is not installed.
   ///
   ///
   nothrow string xdgDir(string dirName, lazy string fallback = null) @safe
   {
      import std.process : execute;
      import std.string : strip;

      string varValue;

      try {
         const environmentValue = environment.get("XDG_" ~ dirName ~ "_DIR");
         if (environmentValue !is null) {
            return environmentValue;
         }
         const xdgProcessRes = execute(["xdg-user-dir", dirName]);
         if (xdgProcessRes.status == 0) {
            return strip(xdgProcessRes.output);
         }
         return fallback();
      } catch (Exception) {
         try {
            return fallback();
         } catch (Exception) {
            return null;
         }
      }
   }

   string xdgCache() nothrow @safe
   {
      try {
         const cacheDir = environment.get("XDG_CACHE_HOME", "");
         if (false == empty(cacheDir) && isAbsolute(cacheDir)) {
            return cacheDir;
         }
      } catch (Exception) {
         assert(0, "environment.get should not throw on POSIX");
      }
      return buildPath(posixHome(), ".cache");
   }

   string xdgConfig() nothrow @safe
   {
      try {
         const configDir = environment.get("XDG_CONFIG_HOME", "");
         if (false == empty(configDir) || isAbsolute(configDir)) {
            return configDir;
         }
      } catch (Exception) {
         assert(0, "environment.get should not throw on POSIX");
      }
      return buildPath(posixHome(), ".config");
   }

   string xdgData() nothrow @safe
   {
      try {
         const dataDir = environment.get("XDG_DATA_HOME", "");
         if (false == empty(dataDir) && isAbsolute(dataDir)) {
            return dataDir;
         }
      } catch (Exception) {
         assert(0, "environment.get should not throw on POISX");
      }
      return buildPath(posixHome(), ".local", "share");
   }

   string xdgRuntime(string subPath = null) nothrow @safe
   {
      try {
         const runtimeDir = environment.get("XDG_RUNTIME_DIR", "");
         if (false == empty(runtimeDir) && isAbsolute(runtimeDir)) {
            return (subPath is null) ? runtimeDir : buildPath(runtimeDir, subPath);
         }
      } catch (Exception) {
         assert(0, "environment.get should not throw on POSIX");
      }
      // XDG Base Directory Specification states that if XDG_RUNTIME_DIR
      // is not set, then applications SHOULD fall back to a replacement
      // directory.  As this may change per OS, an empty string is returned.
      return "";
   }

   string xdgStateDir() nothrow @safe
   {
      try {
         const stateEnv = environment.get("XDG_STATE_HOME", "");
         if (!empty(stateEnv) && isAbsolute(stateEnv)) {
            return stateEnv;
         }
      } catch (Exception) {
         assert(0, "environment.get should not throw on POSIX");
      }
      return buildPath(posixHome(), ".local", "state");
   }

   const(char)* _posix_fallback_home() nothrow @trusted
   {
      passwd* pw = getpwuid(getuid());

      if (pw is null) {
         return null;
      }

      return pw.pw_dir;
   }

   @system @nogc extern (C) nothrow
   {
      /* <bits/types.h> */
      alias gid_t = uint;
      alias uid_t = uint;

      /* <pwd.h> */
      struct passwd
      {
         /// Username
         char* pw_name;
         /// Hashed passphrase, if shadow database is not in use
         char* pw_password;
         /// User ID
         uid_t pw_uid;
         /// Group ID
         gid_t pw_gid;
         /// "Real" name
         char* pw_gecos;
         /// Home directory
         char* pw_dir;
         /// Shell program
         char* pw_shell;
      }

      /// Retrieve the user database entry for the given user ID
      extern passwd* getpwuid(uid_t uid);

      /* <unistd.h> */
      /// Returns the real user ID of the calling process.
      extern uid_t getuid();
   }
} // end of version (Posix)

version (Windows) {
   import std.path : buildPath;
   import std.process : environment;
   import std.string : empty;

   pragma(lib, "ole32");

   string windowsHome() nothrow @trusted
   {
      try {
         const userProfile = environment.get("USERPROFILE");
         if (null !is userProfile || !empty(userProfile)) {
            return userProfile;
         }

         const homeDrive = environment.get("HOMEDRIVE");
         const homePath = environment.get("HOMEPATH");
         if ((null !is homeDrive && null !is homePath) &&
             (false == empty(homeDrive) && false == empty(homePath))) {
               return homeDrive ~ homePath;
             }
      } catch (Exception) {
         // Silent fail. Could only be because of some Windows Error
      }

      return GetKnownFolder(FOLDERID_Profile);
   }

   string windowsRoamingData() nothrow @trusted
   {
      return GetKnownFolder(FOLDERID_RoamingAppData);
   }

   string windowsLocalData() nothrow @trusted
   {
      return GetKnownFolder(FOLDERID_LocalAppData);
   }

   /* Helpers */
   import core.sys.windows.basetyps;
   import core.sys.windows.objbase;
   import core.sys.windows.windef;
   import core.sys.windows.winnls;

   alias KNOWNFOLDERID = GUID;
   alias REFKNOWNFOLDERID = KNOWNFOLDERID*;

   extern(C) nothrow @nogc @system
      HRESULT SHGetKnownFolderPath(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);

   // {B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
   GUID FOLDERID_Desktop = GUID(0xB4BFCC3A, 0xDB2C, 0x424C, [0xB0, 0x29, 0x7F, 0xE9, 0x9A, 0x87, 0xC6, 0x41]);

   // {FDD39AD0-238F-46AF-ADB4-6C85480369C7}
   GUID FOLDERID_Documents = GUID(0xFDD39AD0, 0x238F, 0x46AF, [0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7]);

   // {A63293E8-664E-48DB-A079-DF759E0509F7}
   GUID FOLDERID_Templates = GUID(0xA63293E8, 0x664E, 0x48DB, [0xA0, 0x79, 0xDF, 0x75, 0x9E, 0x05, 0x09, 0xF7]);

   // {3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}
   GUID FOLDERID_RoamingAppData = GUID(0x3EB685DB, 0x65F9, 0x4CF6, [0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D]);

   // {F1B32785-6FBA-4FCF-9D55-7B8E7F157091}
   GUID FOLDERID_LocalAppData = GUID(0xF1B32785, 0x6FBA, 0x4FCF, [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91]);

   // {5E6C858F-0E22-4760-9AFE-EA3317B67173}
   GUID FOLDERID_Profile = GUID(0x5E6C858F, 0x0E22, 0x4760, [0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73]);

   // {33E28130-4E1E-4676-835A-98395C3BC3BB}
   GUID FOLDERID_Pictures = GUID(0x33E28130, 0x4E1E, 0x4676, [0x83, 0x5A, 0x98, 0x39, 0x5C, 0x3B, 0xC3, 0xBB]);

   // {4BD8D571-6D19-48D3-BE97-422220080E43}
   GUID FOLDERID_Music = GUID(0x4BD8D571, 0x6D19, 0x48D3, [0xBE, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0E, 0x43]);

   // {18989B1D-99B5-455B-841C-AB7C74E4DDFC}
   GUID FOLDERID_Videos = GUID(0x18989B1D, 0x99B5, 0x455B, [0x84, 0x1C, 0xAB, 0x7C, 0x74, 0xE4, 0xDD, 0xFC]);

   // {DFDF76A2-C82A-4D63-906A-5644AC457385}
   GUID FOLDERID_Public = GUID(0xDFDF76A2, 0xC82A, 0x4D63, [0x90, 0x6A, 0x56, 0x44, 0xAC, 0x45, 0x73, 0x85]);

   // {374DE290-123F-4565-9164-39C4925E467B}
   GUID FOLDERID_Downloads = GUID(0x374de290, 0x123f, 0x4565, [0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b]);

   string GetKnownFolder(KNOWNFOLDERID rdif) nothrow @trusted
   {
      PWSTR path;
      HRESULT status;
      ULONG bufferSize = 0;

      status = SHGetKnownFolderPath(&rdif, 0, null, &path);
      if (status != S_OK) {
         CoTaskMemFree(path);
         return "";
      }
      scope(exit) CoTaskMemFree(path);

      UnicodeToAnsiSize(path, bufferSize);
      // -1 to remove the null character which D doesn't use
      char[] str = new char[bufferSize - 1];
      WideCharToMultiByte(CP_UTF8, 0, path, -1, str.ptr, bufferSize - 1, null, null);

      return cast(string)str;
   }

   void UnicodeToAnsiSize(in PWCHAR UnicodeString, out ULONG AnsiSizeInBytes) nothrow @trusted
   {
      AnsiSizeInBytes = WideCharToMultiByte(CP_UTF8, 0, UnicodeString, -1, null, 0, null, null);
   }
} // end of version (Windows)
